/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * 页面加载时执行的编辑器构造函数。
 * 创建一个新的编辑器实例，负责管理图形编辑器的核心功能。
 *
 * @param {boolean} chromeless - 是否启用无边框模式，true表示不显示工具栏和菜单
 * @param {Object} themes - 图形主题配置对象
 * @param {mxGraphModel} model - 图形数据模型
 * @param {mxGraph} graph - 图形实例，如果为null则会自动创建
 * @param {boolean} editable - 是否允许编辑图形
 */
Editor = function (chromeless, themes, model, graph, editable) {
    // 调用父类构造函数，继承事件源功能
    mxEventSource.call(this);

    // 设置无边框模式配置
    this.chromeless = (chromeless != null) ? chromeless : this.chromeless;

    // 初始化模板注册表，加载图形形状模板
    this.initStencilRegistry();

    // 创建或使用传入的图形实例
    this.graph = graph || this.createGraph(themes, model);

    // 设置编辑模式，默认为无边框模式的相反值
    this.editable = (editable != null) ? editable : !chromeless;

    // 创建撤销管理器，用于支持撤销/重做功能
    this.undoManager = this.createUndoManager();

    // 初始化编辑器状态为空字符串
    this.status = '';

    /**
     * 获取或创建文件名。
     * 如果已存在文件名则返回，否则生成默认文件名。
     *
     * @returns {string} 文件名
     */
    this.getOrCreateFilename = function () {
        return this.filename || mxResources.get('drawing', [Editor.pageCounter]) + '.xml';
    };

    /**
     * 获取当前文件名。
     *
     * @returns {string} 当前文件名
     */
    this.getFilename = function () {
        return this.filename;
    };

    /**
     * 设置编辑器状态并触发状态变更事件。
     *
     * @param {string} value - 新的状态值
     */
    this.setStatus = function (value) {
        this.status = value;
        this.fireEvent(new mxEventObject('statusChanged'));
    };

    /**
     * 获取当前编辑器状态。
     *
     * @returns {string} 当前状态值
     */
    this.getStatus = function () {
        return this.status;
    };

    /**
     * 图形变更监听器，当图形发生变化时更新修改状态。
     * 用于监听图形模型的变化事件，并在需要时标记文档为已修改状态。
     *
     * @param {Object} sender - 事件发送者
     * @param {mxEventObject} eventObject - 事件对象
     */
    this.graphChangeListener = function (sender, eventObject) {
        // 从事件对象中获取编辑信息
        var edit = (eventObject != null) ? eventObject.getProperty('edit') : null;

        // isSetingGraphXml 初始化xml时，不记录画布更新
        // 只有在非初始化XML状态且编辑操作不被忽略时才标记为已修改
        if (window.isSetingGraphXml === false && (edit == null || !edit.ignoreEdit)) {
            this.setModified(true);
        }
    };

    // 为图形模型添加变更监听器，监听图形变化事件
    this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function () {
        this.graphChangeListener.apply(this, arguments);
    }));

    // 设置持久化图形状态默认值
    // 禁用根节点变化时重置视图，避免不必要的视图重置
    this.graph.resetViewOnRootChange = false;

    // 执行编辑器初始化
    this.init();
};

/**
 * 已打开的编辑器标签页计数器（必须为全局变量以支持跨窗口访问）。
 * 用于生成唯一的文件名，避免文件覆盖。
 */
Editor.pageCounter = 0;

// Firefox 不允许跨域窗口访问，如果从其他域打开则会失败
// 这个自执行函数用于在窗口链中同步 pageCounter
(function () {
    try {
        // 从当前窗口开始遍历窗口链
        var op = window;

        // 遍历窗口链，查找第一个有 Editor.pageCounter 的窗口
        // 避免在 Firefox 中可能的无限循环 (https://drawio.atlassian.net/browse/DS-795)
        while (op.opener != null && typeof op.opener.Editor !== 'undefined' &&
            !isNaN(op.opener.Editor.pageCounter) &&
            op.opener != op) {
            op = op.opener;
        }

        // 在窗口链的第一个窗口中递增计数器
        if (op != null) {
            op.Editor.pageCounter++;
            Editor.pageCounter = op.Editor.pageCounter;
        }
    } catch (e) {
        // 忽略跨域访问错误
    }
})();

/**
 * 指定是否应该使用本地存储（例如在 iPad 上，因为它没有文件系统）。
 * 在支持 Storage API 且为 iOS 设备时启用本地存储。
 */
Editor.useLocalStorage = typeof (Storage) != 'undefined' && mxClient.IS_IOS;

/**
 * 移动工具的图标图像。
 * 根据客户端是否支持SVG选择相应的图像格式。
 */
Editor.moveImage = (mxClient.IS_SVG) ? '' :
    IMAGE_PATH + '/move.png';

/**
 * 行移动操作的拖拽手柄图像。
 * 用于表格行拖拽调整时显示的视觉提示图标。
 */
Editor.rowMoveImage = (mxClient.IS_SVG) ? '' :
    IMAGE_PATH + '/thumb_horz.png';

/**
 * 帮助按钮的图标图像。
 * 用于灯箱视图和嵌入式工具栏的帮助功能图标。
 */
Editor.helpImage = (mxClient.IS_SVG) ? '' :
    IMAGE_PATH + '/help.png';

/**
 * 勾选标记的图标图像。
 * 用于表示选中或确认状态的视觉标识。
 */
Editor.checkmarkImage = (mxClient.IS_SVG) ? '' :
    IMAGE_PATH + '/checkmark.gif';

/**
 * 最大化按钮的图标图像。
 * 用于灯箱视图和嵌入式工具栏的窗口最大化功能。
 */
Editor.maximizeImage = '';

/**
 * 缩小功能的图标图像。
 * 用于图形编辑器中的缩小视图操作。
 */
Editor.zoomOutImage = '';

/**
 * 放大功能的图标图像。
 * 用于图形编辑器中的放大视图操作。
 */
Editor.zoomInImage = '';

/**
 * 适合窗口缩放功能的图标图像。
 * 用于调整图形视图以适应当前窗口大小。
 */
Editor.zoomFitImage = '';

/**
 * 图层管理的图标图像。
 * 用于显示和管理图形编辑器中的图层功能。
 */
Editor.layersImage = '';

/**
 * 上一页导航的图标图像。
 * 用于多页文档中的页面导航功能。
 */
Editor.previousImage = '';

/**
 * 下一页导航的图标图像。
 * 用于多页文档中的页面导航功能。
 */
Editor.nextImage = '';

/**
 * 编辑功能的图标图像。
 * 用于启动编辑模式或编辑操作的视觉标识。
 */
Editor.editImage = (mxClient.IS_SVG) ? '' : IMAGE_PATH + '/edit.gif';

/**
 * 大尺寸缩小图标图像。
 * 用于工具栏中较大的缩小功能按钮。
 */
Editor.zoomOutLargeImage = '';

/**
 * 大尺寸放大图标图像。
 * 用于工具栏中较大的放大功能按钮。
 */
Editor.zoomInLargeImage = '';

/**
 * 实际尺寸显示的大图标图像。
 * 用于将图形视图恢复到100%原始尺寸的功能按钮。
 */
Editor.actualSizeLargeImage = '';

/**
 * 打印功能的大图标图像。
 * 用于图形编辑器中的打印操作按钮。
 */
Editor.printLargeImage = '';

/**
 * 图层管理的大图标图像。
 * 用于显示和管理图形编辑器中的图层功能的大尺寸按钮。
 */
Editor.layersLargeImage = '';

/**
 * 关闭按钮的大图标图像。
 * 用于关闭窗口或面板的操作按钮。
 */
Editor.closeLargeImage = '';

/**
 * 编辑功能的大图标图像。
 * 用于启动编辑模式或编辑操作的大尺寸按钮。
 */
Editor.editLargeImage = '';

/**
 * 上一页导航的大图标图像。
 * 用于多页文档中的页面导航功能的大尺寸按钮。
 */
Editor.previousLargeImage = '';

/**
 * 下一页导航的大图标图像。
 * 用于多页文档中的页面导航功能的大尺寸按钮。
 */
Editor.nextLargeImage = '';

/**
 * 刷新按钮的大图标图像。
 * 用于刷新页面或重新加载内容的功能按钮。
 */
Editor.refreshLargeImage = '';

/**
 * 返回按钮的大图标图像。
 * 用于返回上一个页面或退出当前操作的功能按钮。
 */
Editor.backLargeImage = '';

/**
 * 全屏显示的大图标图像。
 * 用于切换到全屏模式显示的功能按钮。
 */
Editor.fullscreenLargeImage = '';

/**
 * rough.js 支持的所有填充样式。
 * 用于图形编辑器的手绘风格填充选项。
 */
Editor.roughFillStyles = [{
        val: 'auto',
        dispName: 'Auto'
    }, {
        val: 'hachure',
        dispName: 'Hachure'
    }, {
        val: 'solid',
        dispName: 'Solid'
    },
    {
        val: 'zigzag',
        dispName: 'ZigZag'
    }, {
        val: 'cross-hatch',
        dispName: 'Cross Hatch'
    }, {
        val: 'dots',
        dispName: 'Dots'
    },
    {
        val: 'dashed',
        dispName: 'Dashed'
    }, {
        val: 'zigzag-line',
        dispName: 'ZigZag Line'
    }
];

/**
 * 格式面板的图形主题配置。
 * 用于存储可用的图形主题选项。
 */
Editor.themes = null;

/**
 * 根据操作系统确定控制键名称。
 * 在 Mac 系统上为 'Cmd'，其他系统上为 'Ctrl'。
 */
Editor.ctrlKey = (mxClient.IS_MAC) ? 'Cmd' : 'Ctrl';

/**
 * 工具提示的偏移距离（像素）。
 * 控制工具提示相对于鼠标位置的显示偏移量。
 */
Editor.hintOffset = 20;

/**
 * 指定是否允许显示弹窗。默认为 true。
 * 控制应用程序是否可以显示各种弹窗和对话框。
 */
Editor.popupsAllowed = true;

/**
 * 指定是否启用原生剪贴板功能。
 * 在非 Firefox 浏览器且支持 Clipboard API 时启用。
 */
Editor.enableNativeCipboard = !mxClient.IS_FF && navigator.clipboard != null;

/**
 * Editor 类继承自 mxEventSource
 * 使其具备事件监听和触发的能力
 */
mxUtils.extend(Editor, mxEventSource);

/**
 * 存储 mxClient.NO_FO 的初始状态。
 * 用于记录客户端是否支持外来对象的初始配置。
 */
Editor.prototype.originalNoForeignObject = mxClient.NO_FO;

/**
 * 用于透明背景的图片URL。
 * 根据客户端是否支持SVG选择不同的图片格式。
 */
Editor.prototype.transparentImage = (mxClient.IS_SVG) ? '' :
    IMAGE_PATH + '/transparent.gif';

/**
 * 指定是否应该在所有方向上扩展画布。默认为 true。
 * 控制画布是否可以自动扩展以适应内容。
 */
Editor.prototype.extendCanvas = true;

/**
 * 指定应用程序是否应该以无边框模式运行。默认为 false。
 * 此默认值仅在构造函数参数为 null 时使用。
 */
Editor.prototype.chromeless = false;

/**
 * 指定对话框中确定/取消按钮的顺序。默认为 true。
 * 在 Mac 系统上取消按钮在前，Windows/Confluence 使用取消按钮在后。
 */
Editor.prototype.cancelFirst = true;

/**
 * 指定编辑器是否启用。默认为 true。
 * 控制编辑器是否接受用户输入和操作。
 */
Editor.prototype.enabled = true;

/**
 * 包含最后一次保存时使用的文件名。默认值为 null。
 * 用于跟踪当前正在编辑的文件的名称。
 */
Editor.prototype.filename = null;

/**
 * 包含图表的当前修改状态。
 * 对于新图表和保存后的图表，此值为 false。
 */
Editor.prototype.modified = false;

/**
 * 指定图表是否应该尽可能自动保存。默认为 true。
 * 控制是否启用自动保存功能。
 */
Editor.prototype.autosave = true;

/**
 * 指定初始页面视图的顶部间距。默认为 0。
 * 控制页面顶部预留的空间大小。
 */
Editor.prototype.initialTopSpacing = 0;

/**
 * 指定应用程序名称。默认为 document.title。
 * 用于显示在界面和标题中的应用名称。
 */
Editor.prototype.appName = document.title;

/**
 * 编辑空白文件的URL。
 * 用于创建新空白文档时的基础URL。
 */
Editor.prototype.editBlankUrl = window.location.protocol + '//' + window.location.host + '/';

/**
 * 图形容器溢出样式的默认值。
 * 控制图形容器如何处理超出边界的内容。
 */
Editor.prototype.defaultGraphOverflow = 'hidden';

/**
 * 初始化编辑器环境。
 * 设置编辑器的基础配置和事件监听器。
 */
Editor.prototype.init = function () {};

/**
 * 检查当前是否为无边框视图模式。
 *
 * @returns {boolean} 如果为无边框模式则返回 true
 */
Editor.prototype.isChromelessView = function () {
    return this.chromeless;
};

/**
 * 设置自动保存功能的状态。
 *
 * @param {boolean} value - 是否启用自动保存
 */
Editor.prototype.setAutosave = function (value) {
    this.autosave = value;
    this.fireEvent(new mxEventObject('autosaveChanged'));
};

/**
 * 获取编辑空白文件的完整URL。
 *
 * @param {string} params - URL参数字符串
 * @returns {string} 完整的编辑URL
 */
Editor.prototype.getEditBlankUrl = function (params) {
    return this.editBlankUrl + params;
}

/**
 * 在新窗口中打开XML内容进行编辑。
 * 使用postMessage API在新窗口中传递XML数据。
 *
 * @param {string} xml - 要编辑的XML内容
 * @param {string} title - 新文档的标题
 */
Editor.prototype.editAsNew = function (xml, title) {
    // 构建URL参数，包含标题信息
    var p = (title != null) ? '?title=' + encodeURIComponent(title) : '';

    // 如果存在UI参数，则添加到URL中
    if (urlParams['ui'] != null) {
        p += ((p.length > 0) ? '&' : '?') + 'ui=' + urlParams['ui'];
    }

    // 检查是否支持postMessage API（用于跨窗口通信）
    if (typeof window.postMessage !== 'undefined' &&
        (document.documentMode == null ||
            document.documentMode >= 10)) {
        var wnd = null;

        // 创建消息监听器，等待新窗口准备就绪
        var l = mxUtils.bind(this, function (evt) {
            if (evt.data == 'ready' && evt.source == wnd) {
                // 新窗口已准备就绪，发送XML数据
                mxEvent.removeListener(window, 'message', l);
                wnd.postMessage(xml, '*');
            }
        });

        // 添加消息监听器
        mxEvent.addListener(window, 'message', l);
        // 打开新窗口进行编辑
        wnd = this.graph.openLink(this.getEditBlankUrl(
            p + ((p.length > 0) ? '&' : '?') +
            'client=1'), null, true);
    } else {
        this.graph.openLink(this.getEditBlankUrl(p) +
            '#R' + encodeURIComponent(xml));
    }
};

/**
 * 创建图形实例。
 * 初始化图形编辑器的核心图形对象，配置主题和模型。
 *
 * @param {Object} themes - 图形主题配置
 * @param {mxGraphModel} model - 图形数据模型
 * @returns {Graph} 新创建的图形实例
 */
Editor.prototype.createGraph = function (themes, model) {
    // 创建图形实例，使用提供的主题和模型
    var graph = new Graph(null, model, null, null, themes);
    // 设置不使用透明背景
    graph.transparentBackground = false;

    // 在非无边框模式下，所有链接都在新窗口中打开
    if (!this.chromeless) {
        graph.isBlankLink = function (href) {
            return !this.isExternalProtocol(href);
        };
    }

    return graph;
};

/**
 * 重置图形到默认状态。
 * 恢复图形的所有配置到初始值，包括网格、工具提示、连接等功能。
 */
Editor.prototype.resetGraph = function () {
    // 根据视图模式和URL参数设置网格是否启用
    this.graph.gridEnabled = !this.isChromelessView() || urlParams['grid'] == '1';

    // 启用辅助线功能
    this.graph.graphHandler.guidesEnabled = true;

    // 启用工具提示
    this.graph.setTooltips(true);

    // 启用连接功能（允许创建边）
    this.graph.setConnectable(true);

    // 启用折叠功能
    this.graph.foldingEnabled = true;

    // 恢复默认滚动条设置
    this.graph.scrollbars = this.graph.defaultScrollbars;

    // 设置页面可见性
    this.graph.pageVisible = this.graph.defaultPageVisible;
    this.graph.pageBreaksVisible = this.graph.pageVisible;
    this.graph.preferPageSize = this.graph.pageBreaksVisible;

    // 清除背景设置
    this.graph.background = null;

    // 恢复默认页面缩放和格式
    this.graph.pageScale = mxGraph.prototype.pageScale;
    this.graph.pageFormat = mxGraph.prototype.pageFormat;

    // 重置当前缩放和偏移
    this.graph.currentScale = 1;
    this.graph.currentTranslate.x = 0;
    this.graph.currentTranslate.y = 0;

    // 更新图形组件
    this.updateGraphComponents();

    // 设置视图缩放为1
    this.graph.view.setScale(1);
};

/**
 * 从XML节点读取图形状态设置。
 * 从保存的XML配置中恢复图形的各种状态和设置。
 *
 * @param {Element} node - 包含状态信息的XML节点
 */
Editor.prototype.readGraphState = function (node) {
    // 从XML属性读取网格设置
    this.graph.gridEnabled = node.getAttribute('grid') != '0' && (!this.isChromelessView() || urlParams['grid'] == '1');
    this.graph.gridSize = parseFloat(node.getAttribute('gridSize')) || mxGraph.prototype.gridSize;

    // 读取辅助线、工具提示、连接等功能设置
    this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
    this.graph.setTooltips(node.getAttribute('tooltips') != '0');
    this.graph.setConnectable(node.getAttribute('connect') != '0');
    this.graph.connectionArrowsEnabled = node.getAttribute('arrows') != '0';
    this.graph.foldingEnabled = node.getAttribute('fold') != '0';

    // 在无边框视图下，根据URL参数决定是否启用折叠功能
    if (this.isChromelessView() && this.graph.foldingEnabled) {
        this.graph.foldingEnabled = urlParams['nav'] == '1';
        this.graph.cellRenderer.forceControlClickHandler = this.graph.foldingEnabled;
    }

    // 读取页面缩放设置
    var ps = parseFloat(node.getAttribute('pageScale'));

    if (!isNaN(ps) && ps > 0) {
        this.graph.pageScale = ps;
    } else {
        this.graph.pageScale = mxGraph.prototype.pageScale;
    }

    // 读取页面可见性设置（仅在非灯箱视图和非查看器模式下）
    if (!this.graph.isLightboxView() && !this.graph.isViewer()) {
        var pv = node.getAttribute('page');

        if (pv != null) {
            this.graph.pageVisible = (pv != '0');
        } else {
            this.graph.pageVisible = this.graph.defaultPageVisible;
        }
    } else {
        this.graph.pageVisible = false;
    }

    this.graph.pageBreaksVisible = this.graph.pageVisible;
    this.graph.preferPageSize = this.graph.pageBreaksVisible;

    // 读取页面尺寸设置
    var pw = parseFloat(node.getAttribute('pageWidth'));
    var ph = parseFloat(node.getAttribute('pageHeight'));

    if (!isNaN(pw) && !isNaN(ph)) {
        this.graph.pageFormat = new mxRectangle(0, 0, pw, ph);
    }

    // 读取持久化状态设置（背景）
    var bg = node.getAttribute('background');

    if (bg != null && bg.length > 0) {
        this.graph.background = bg;
    } else {
        this.graph.background = null;
    }
};

/**
 * 设置当前图表的XML节点。
 * 从XML节点加载图形数据，支持mxGraphModel和root节点格式。
 *
 * @param {Element} node - 包含图形数据的XML节点，如果为null则重置图形
 */
Editor.prototype.setGraphXml = function (node) {
    if (node != null) {
        // 创建XML解码器
        var dec = new mxCodec(node.ownerDocument);

        if (node.nodeName == 'mxGraphModel') {
            // 处理mxGraphModel格式的XML
            this.graph.model.beginUpdate();

            try {
                // 清除现有模型数据
                this.graph.model.clear();
                // 重置视图缩放
                this.graph.view.scale = 1;
                // 读取图形状态设置
                this.readGraphState(node);
                // 更新图形组件
                this.updateGraphComponents();
                // 解码XML到图形模型
                dec.decode(node, this.graph.getModel());
            } finally {
                this.graph.model.endUpdate();
            }

        } else if (node.nodeName == 'root') {
            // 处理root格式的XML
            this.resetGraph();

            // Firefox 20中mxUtils.getXml错误的变通方案
            var wrapper = dec.document.createElement('mxGraphModel');
            wrapper.appendChild(node);

            dec.decode(wrapper, this.graph.getModel());
            this.updateGraphComponents();
        } else {
            // 不支持的XML格式，抛出异常
            throw {
                message: mxResources.get('cannotOpenFile'),
                node: node,
                toString: function () {
                    return this.message;
                }
            };
        }
    } else {
        // 节点为空，重置图形
        this.resetGraph();
        this.graph.model.clear();
    }

    // 刷新格式设置
    Format.prototype.refresh = Format.prototype.oldRefresh || (() => {})
    window.myEditor && window.myEditor.format && window.myEditor.format.refresh()

    // 异步触发事件
    Promise.resolve().then(() => {
        this.fireEvent(new mxEventObject('resetGraphView'));
        drawioBus.$emit('afterSetGraphXml')
    })
};

/**
 * 获取表示当前图表的XML节点。
 * 将图形数据编码为XML格式，支持包含或排除选择状态。
 *
 * @param {boolean} ignoreSelection - 是否忽略选择状态，默认为true
 * @returns {Element} 包含图形数据的XML节点
 */
Editor.prototype.getGraphXml = function (ignoreSelection) {
    // 设置默认参数
    ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
    var node = null;

    if (ignoreSelection) {
        // 忽略选择状态，编码整个图形模型
        var enc = new mxCodec(mxUtils.createXmlDocument());
        node = enc.encode(this.graph.getModel());
    } else {
        // 只编码选中的单元格
        node = this.graph.encodeCells(mxUtils.sortCells(this.graph.model.getTopmostCells(
            this.graph.getSelectionCells())));
    }

    // 添加视图偏移信息
    if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0) {
        node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
        node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
    }

    // 添加图形状态属性
    node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
    node.setAttribute('gridSize', this.graph.gridSize);
    node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
    node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
    node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
    node.setAttribute('arrows', (this.graph.connectionArrowsEnabled) ? '1' : '0');
    node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
    node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
    node.setAttribute('pageScale', this.graph.pageScale);
    node.setAttribute('pageWidth', this.graph.pageFormat.width);
    node.setAttribute('pageHeight', this.graph.pageFormat.height);

    // 添加背景设置
    if (this.graph.background != null) {
        node.setAttribute('background', this.graph.background);
    }

    return node;
};

/**
 * 获取指定单元格的XML表示。
 * 将单元格数组编码为XML格式。
 *
 * @param {Array} cells - 要编码的单元格数组
 * @returns {Element} 包含单元格数据的XML节点
 */
Editor.prototype.getXmlByCells = function (cells) {
    var codec = new mxCodec();
    return codec.encode(cells);
};

/**
 * 根据对象ID获取单元格的XML。
 * 在指定的单元格或当前选中单元格中查找具有指定对象ID的子单元格。
 *
 * @param {mxCell} cell - 要搜索的单元格，不传则使用当前选中单元格
 * @param {string} objectId - 要查找的单元格对象ID
 * @returns {string} 找到的单元格XML字符串
 */
Editor.prototype.getXmlByCellObjectId = function (cell, objectId) {
    let currentCell = cell || window.myEditor.editor.graph.getSelectionCell()

    // 只传了cell  直接获取cell的xml
    if (!objectId && cell) {
        return mxUtils.getXml(Editor.prototype.getXmlByCells(cell));
    }

    if (!(currentCell.value instanceof Object) || currentCell.value.getAttribute("objectId") !== objectId) {
        const findCellInChild = (cellList) => {
            cellList.some(cell => {
                if (cell.value instanceof Object && cell.value.getAttribute("objectId") == objectId) {
                    currentCell = cell
                    return true
                }
                findCellInChild(cellList.children || [])
                return false
            })
        }
        // 递归从 children 里面找
        findCellInChild(currentCell.children || [])
    }

    const nodeXml = Editor.prototype.getXmlByCells(currentCell);
    return mxUtils.getXml(nodeXml);
};



/**
 * 保持图形容器与持久化图形状态同步。
 * 此注释位于updateGraphComponents方法上方。
 */
/**
 * 更新图形组件。
 * 验证图形背景并根据滚动条设置调整容器溢出样式。
 */
Editor.prototype.updateGraphComponents = function () {
    var graph = this.graph;

    if (graph.container != null) {
        // 验证图形视图背景
        graph.view.validateBackground();

        // 根据滚动条设置调整容器溢出样式
        graph.container.style.overflow = (graph.scrollbars) ? 'auto' : this.defaultGraphOverflow;

        // 触发图形组件更新事件
        this.fireEvent(new mxEventObject('updateGraphComponents'));
    }
};

/**
 * 设置修改状态标记。
 * 更新编辑器的修改状态，并相应地更新UI元素的外观。
 *
 * @param {boolean} value - 是否已修改
 */
Editor.prototype.setModified = function (value) {
    this.modified = value;

    // 更新保存按钮图标
    const saveTool = document.querySelector('.geSprite-save')
    if (saveTool) {
        saveTool.style.backgroundImage = `url(images/${value ? 'save2-error' : 'save2'}.png)`
    }

    // 更新UML排序按钮状态
    const umlsortBtn = document.querySelector('#gesprite-umlsort')
    if (umlsortBtn) {
        umlsortBtn.classList.toggle('mxDisabled', this.modified)
    }
};

/**
 * 设置文件名。
 * 更新编辑器当前正在编辑的文件的名称。
 *
 * @param {string} value - 新的文件名
 */
Editor.prototype.setFilename = function (value) {
    this.filename = value;
};

/**
 * 创建并返回新的撤销管理器。
 * 设置撤销/重做功能，并保持选择状态与历史记录同步。
 *
 * @returns {mxUndoManager} 新创建的撤销管理器实例
 */
Editor.prototype.createUndoManager = function () {
    var graph = this.graph;
    var undoMgr = new mxUndoManager();

    // 创建撤销监听器，将编辑操作添加到撤销管理器
    this.undoListener = function (sender, evt) {
        undoMgr.undoableEditHappened(evt.getProperty('edit'));
    };

    // 安装命令历史记录监听器
    var listener = mxUtils.bind(this, function (sender, evt) {
        this.undoListener.apply(this, arguments);
    });

    // 为图形模型和视图添加撤销事件监听器
    graph.getModel().addListener(mxEvent.UNDO, listener);
    graph.getView().addListener(mxEvent.UNDO, listener);

    // 保持选择状态与撤销历史同步
    var undoHandler = function (sender, evt) {
        var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes, function (change) {
            // 只选择对单元格层次结构的变化
            return !(change instanceof mxChildChange);
        });

        if (cand.length > 0) {
            var model = graph.getModel();
            var cells = [];

            for (var i = 0; i < cand.length; i++) {
                if (graph.view.getState(cand[i]) != null) {
                    cells.push(cand[i]);
                }
            }

            graph.setSelectionCells(cells);
        }
    };

    // 为撤销管理器添加撤销和重做事件监听器
    undoMgr.addListener(mxEvent.UNDO, undoHandler);
    undoMgr.addListener(mxEvent.REDO, undoHandler);

    return undoMgr;
};

/**
 * 初始化模板注册表。
 * 添加基本的模板集合（无命名空间）。
 */
Editor.prototype.initStencilRegistry = function () {};

/**
 * 销毁编辑器实例。
 * 清理图形实例并释放相关资源。
 */
Editor.prototype.destroy = function () {
    if (this.graph != null) {
        // 销毁图形实例
        this.graph.destroy();
        this.graph = null;
    }
};

/**
 * 异步打开新窗口并同时加载文件的类。
 * 这个类作为打开对话框和新编辑器之间的桥梁。
 *
 * @param {Function} done - 操作完成时的回调函数
 */
OpenFile = function (done) {
    this.producer = null;
    this.consumer = null;
    this.done = done;
    this.args = null;
};

/**
 * 注册来自新窗口的编辑器。
 *
 * @param {Object} value - 编辑器实例
 */
OpenFile.prototype.setConsumer = function (value) {
    this.consumer = value;
    this.execute();
};

/**
 * 设置从已加载文件中获取的数据。
 */
OpenFile.prototype.setData = function () {
    this.args = arguments;
    this.execute();
};

/**
 * 显示错误消息。
 *
 * @param {string} msg - 错误消息
 */
OpenFile.prototype.error = function (msg) {
    this.cancel(true);
    mxUtils.alert(msg);
};

/**
 * 消费数据。
 * 当消费者和参数都可用时，执行数据消费操作。
 */
OpenFile.prototype.execute = function () {
    if (this.consumer != null && this.args != null) {
        this.cancel(false);
        this.consumer.apply(this, this.args);
    }
};

/**
 * 取消操作。
 *
 * @param {boolean} cancel - 取消标志
 */
OpenFile.prototype.cancel = function (cancel) {
    if (this.done != null) {
        this.done((cancel != null) ? cancel : true);
    }
};

/**
 * 基本对话框，可在查看器中使用（打印对话框）。
 * 创建一个可自定义的对话框组件。
 *
 * @param {Object} editorUi - 编辑器UI实例
 * @param {Element} elt - 对话框内容元素
 * @param {number} w - 对话框宽度
 * @param {number} h - 对话框高度
 * @param {boolean} modal - 是否为模态对话框
 * @param {boolean} closable - 是否可关闭
 * @param {Function} onClose - 关闭时的回调函数
 * @param {boolean} noScroll - 是否禁用滚动
 * @param {boolean} transparent - 是否透明背景
 * @param {Function} onResize - 调整大小时的回调函数
 * @param {boolean} ignoreBgClick - 是否忽略背景点击
 */
function Dialog(editorUi, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick) {
    var dx = 0;
    var w0 = w;
    var h0 = h;

    var ds = mxUtils.getDocumentSize();

    // 修复查看器灯箱中打印对话框偏移的临时解决方案
    if (window.innerHeight != null) {
        ds.height = window.innerHeight;
    }

    var dh = ds.height;
    var left = Math.max(1, Math.round((ds.width - w - 64) / 2));
    var top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));

    // 保持窗口大小在可用空间内
    elt.style.maxHeight = '100%';

    w = (document.body != null) ? Math.min(w, document.body.scrollWidth - 64) : w;
    h = Math.min(h, dh - 64);

    // 递增zIndex以将子对话框和背景置于现有对话框和背景之上
    if (editorUi.dialogs.length > 0) {
        this.zIndex += editorUi.dialogs.length * 2;
    }

    if (this.bg == null) {
        this.bg = editorUi.createDiv('background');
        this.bg.style.position = 'absolute';
        this.bg.style.background = Dialog.backdropColor;
        this.bg.style.height = dh + 'px';
        this.bg.style.right = '0px';
        this.bg.style.zIndex = this.zIndex - 2;

        mxUtils.setOpacity(this.bg, this.bgOpacity);
    }

    var origin = mxUtils.getDocumentScrollOrigin(document);
    this.bg.style.left = origin.x + 'px';
    this.bg.style.top = origin.y + 'px';
    left += origin.x;
    top += origin.y;

    if (modal) {
        document.body.appendChild(this.bg);
    }

    var div = editorUi.createDiv(transparent ? 'geTransDialog' : 'geDialog');
    var pos = this.getPosition(left, top, w, h);
    left = pos.x;
    top = pos.y;

    div.style.width = w + 'px';
    div.style.height = h + 'px';
    div.style.left = left + 'px';
    div.style.top = top + 'px';
    div.style.zIndex = this.zIndex;

    div.appendChild(elt);
    document.body.appendChild(div);

    // 如果需要，添加垂直滚动条
    if (!noScroll && elt.clientHeight > div.clientHeight - 64) {
        elt.style.overflowY = 'auto';
    }

    if (closable) {
        var img = document.createElement('img');

        img.setAttribute('src', Dialog.prototype.closeImage);
        img.setAttribute('title', mxResources.get('close'));
        img.className = 'geDialogClose';
        img.style.top = (top + 14) + 'px';
        img.style.left = (left + w + 38 - dx) + 'px';
        img.style.zIndex = this.zIndex;

        mxEvent.addListener(img, 'click', mxUtils.bind(this, function () {
            editorUi.hideDialog(true);
        }));

        document.body.appendChild(img);
        this.dialogImg = img;

        if (!ignoreBgClick) {
            var mouseDownSeen = false;

            mxEvent.addGestureListeners(this.bg, mxUtils.bind(this, function (evt) {
                mouseDownSeen = true;
            }), null, mxUtils.bind(this, function (evt) {
                if (mouseDownSeen) {
                    editorUi.hideDialog(true);
                    mouseDownSeen = false;
                }
            }));
        }
    }

    this.resizeListener = mxUtils.bind(this, function () {
        if (onResize != null) {
            var newWH = onResize();

            if (newWH != null) {
                w0 = w = newWH.w;
                h0 = h = newWH.h;
            }
        }

        var ds = mxUtils.getDocumentSize();
        dh = ds.height;
        this.bg.style.height = dh + 'px';

        left = Math.max(1, Math.round((ds.width - w - 64) / 2));
        top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
        w = (document.body != null) ? Math.min(w0, document.body.scrollWidth - 64) : w0;
        h = Math.min(h0, dh - 64);

        var pos = this.getPosition(left, top, w, h);
        left = pos.x;
        top = pos.y;

        div.style.left = left + 'px';
        div.style.top = top + 'px';
        div.style.width = w + 'px';
        div.style.height = h + 'px';

        // 如果需要，添加垂直滚动条
        if (!noScroll && elt.clientHeight > div.clientHeight - 64) {
            elt.style.overflowY = 'auto';
        }

        if (this.dialogImg != null) {
            this.dialogImg.style.top = (top + 14) + 'px';
            this.dialogImg.style.left = (left + w + 38 - dx) + 'px';
        }
    });

    mxEvent.addListener(window, 'resize', this.resizeListener);

    this.onDialogClose = onClose;
    this.container = div;

    editorUi.editor.fireEvent(new mxEventObject('showDialog'));
};

/**
 *
 */
Dialog.backdropColor = 'white';

/**
 *
 */
Dialog.prototype.zIndex = mxPopupMenu.prototype.zIndex - 1;

/**
 *
 */
Dialog.prototype.noColorImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/nocolor.png' : '';

/**
 *
 */
Dialog.prototype.closeImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/close.png' : '';

/**
 *
 */
Dialog.prototype.clearImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/clear.gif' : '';

/**
 *
 */
Dialog.prototype.lockedImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/locked.png' : '';

/**
 *
 */
Dialog.prototype.unlockedImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/unlocked.png' : '';

/**
 * 从 DOM 中移除对话框。
 */
Dialog.prototype.bgOpacity = 80;

/**
 * 从 DOM 中移除对话框。
 */
/**
 * 获取对话框位置。
 *
 * @param {number} left - 左侧位置
 * @param {number} top - 顶部位置
 * @returns {mxPoint} 位置坐标点
 */
Dialog.prototype.getPosition = function (left, top) {
    return new mxPoint(left, top);
};

/**
 * 从DOM中移除对话框。
 *
 * @param {boolean} cancel - 是否取消操作
 * @param {boolean} isEsc - 是否通过ESC键关闭
 * @returns {boolean} 如果关闭被阻止则返回false
 */
Dialog.prototype.close = function (cancel, isEsc) {
    if (this.onDialogClose != null) {
        if (this.onDialogClose(cancel, isEsc) == false) {
            return false;
        }

        this.onDialogClose = null;
    }

    if (this.dialogImg != null) {
        this.dialogImg.parentNode.removeChild(this.dialogImg);
        this.dialogImg = null;
    }

    if (this.bg != null && this.bg.parentNode != null) {
        this.bg.parentNode.removeChild(this.bg);
    }

    mxEvent.removeListener(window, 'resize', this.resizeListener);
    this.container.parentNode.removeChild(this.container);
};

/**
 *
 */
var ErrorDialog = function (editorUi, title, message, buttonText, fn, retry, buttonText2, fn2, hide, buttonText3, fn3) {
    hide = (hide != null) ? hide : true;

    var div = document.createElement('div');
    div.style.textAlign = 'center';

    if (title != null) {
        var hd = document.createElement('div');
        hd.style.padding = '0px';
        hd.style.margin = '0px';
        hd.style.fontSize = '18px';
        hd.style.paddingBottom = '16px';
        hd.style.marginBottom = '10px';
        hd.style.borderBottom = '1px solid #c0c0c0';
        hd.style.color = 'gray';
        hd.style.whiteSpace = 'nowrap';
        hd.style.textOverflow = 'ellipsis';
        hd.style.overflow = 'hidden';
        mxUtils.write(hd, title);
        hd.setAttribute('title', title);
        div.appendChild(hd);
    }

    var p2 = document.createElement('div');
    p2.style.lineHeight = '1.2em';
    p2.style.padding = '6px';
    p2.innerHTML = message;
    div.appendChild(p2);

    var btns = document.createElement('div');
    btns.style.marginTop = '12px';
    btns.style.textAlign = 'center';

    if (retry != null) {
        var retryBtn = mxUtils.button(mxResources.get('tryAgain'), function () {
            editorUi.hideDialog();
            retry();
        });
        retryBtn.className = 'geBtn';
        btns.appendChild(retryBtn);

        btns.style.textAlign = 'center';
    }

    if (buttonText3 != null) {
        var btn3 = mxUtils.button(buttonText3, function () {
            if (fn3 != null) {
                fn3();
            }
        });

        btn3.className = 'geBtn';
        btns.appendChild(btn3);
    }

    var btn = mxUtils.button(buttonText, function () {
        if (hide) {
            editorUi.hideDialog();
        }

        if (fn != null) {
            fn();
        }
    });

    btn.className = 'geBtn';
    btns.appendChild(btn);

    if (buttonText2 != null) {
        var mainBtn = mxUtils.button(buttonText2, function () {
            if (hide) {
                editorUi.hideDialog();
            }

            if (fn2 != null) {
                fn2();
            }
        });

        mainBtn.className = 'geBtn gePrimaryBtn';
        btns.appendChild(mainBtn);
    }

    this.init = function () {
        btn.focus();
    };

    div.appendChild(btns);

    this.container = div;
};

/**
 * 构造一个新的打印对话框。
 */
/**
 * 打印对话框构造函数。
 *
 * @param {Object} editorUi - 编辑器UI实例
 * @param {string} title - 对话框标题
 */
var PrintDialog = function (editorUi, title) {
    this.create(editorUi, title);
};

/**
 * 构造新的打印对话框。
 *
 * @param {Object} editorUi - 编辑器UI实例
 */
PrintDialog.prototype.create = function (editorUi) {
    var graph = editorUi.editor.graph;
    var row, td;

    var table = document.createElement('table');
    table.style.width = '100%';
    table.style.height = '100%';
    var tbody = document.createElement('tbody');

    row = document.createElement('tr');

    var onePageCheckBox = document.createElement('input');
    onePageCheckBox.setAttribute('type', 'checkbox');
    td = document.createElement('td');
    td.setAttribute('colspan', '2');
    td.style.fontSize = '10pt';
    td.appendChild(onePageCheckBox);

    var span = document.createElement('span');
    mxUtils.write(span, ' ' + mxResources.get('fitPage'));
    td.appendChild(span);

    mxEvent.addListener(span, 'click', function (evt) {
        onePageCheckBox.checked = !onePageCheckBox.checked;
        pageCountCheckBox.checked = !onePageCheckBox.checked;
        mxEvent.consume(evt);
    });

    mxEvent.addListener(onePageCheckBox, 'change', function () {
        pageCountCheckBox.checked = !onePageCheckBox.checked;
    });

    row.appendChild(td);
    tbody.appendChild(row);

    row = row.cloneNode(false);

    var pageCountCheckBox = document.createElement('input');
    pageCountCheckBox.setAttribute('type', 'checkbox');
    td = document.createElement('td');
    td.style.fontSize = '10pt';
    td.appendChild(pageCountCheckBox);

    var span = document.createElement('span');
    mxUtils.write(span, ' ' + mxResources.get('posterPrint') + ':');
    td.appendChild(span);

    mxEvent.addListener(span, 'click', function (evt) {
        pageCountCheckBox.checked = !pageCountCheckBox.checked;
        onePageCheckBox.checked = !pageCountCheckBox.checked;
        mxEvent.consume(evt);
    });

    row.appendChild(td);

    var pageCountInput = document.createElement('input');
    pageCountInput.setAttribute('value', '1');
    pageCountInput.setAttribute('type', 'number');
    pageCountInput.setAttribute('min', '1');
    pageCountInput.setAttribute('size', '4');
    pageCountInput.setAttribute('disabled', 'disabled');
    pageCountInput.style.width = '50px';

    td = document.createElement('td');
    td.style.fontSize = '10pt';
    td.appendChild(pageCountInput);
    mxUtils.write(td, ' ' + mxResources.get('pages') + ' (max)');
    row.appendChild(td);
    tbody.appendChild(row);

    mxEvent.addListener(pageCountCheckBox, 'change', function () {
        if (pageCountCheckBox.checked) {
            pageCountInput.removeAttribute('disabled');
        } else {
            pageCountInput.setAttribute('disabled', 'disabled');
        }

        onePageCheckBox.checked = !pageCountCheckBox.checked;
    });

    row = row.cloneNode(false);

    td = document.createElement('td');
    mxUtils.write(td, mxResources.get('pageScale') + ':');
    row.appendChild(td);

    td = document.createElement('td');
    var pageScaleInput = document.createElement('input');
    pageScaleInput.setAttribute('value', '100 %');
    pageScaleInput.setAttribute('size', '5');
    pageScaleInput.style.width = '50px';

    td.appendChild(pageScaleInput);
    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.style.paddingTop = '20px';
    td.setAttribute('align', 'right');

    // 打印输出的整体缩放比例，用于考虑对话框等中的打印边框
    function preview(print) {
        var autoOrigin = onePageCheckBox.checked || pageCountCheckBox.checked;
        var printScale = parseInt(pageScaleInput.value) / 100;

        if (isNaN(printScale)) {
            printScale = 1;
            pageScaleInput.value = '100%';
        }

        // 匹配实际打印输出中可用纸张大小的临时解决方案
        printScale *= 0.75;

        var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
        var scale = 1 / graph.pageScale;

        if (autoOrigin) {
            var pageCount = (onePageCheckBox.checked) ? 1 : parseInt(pageCountInput.value);

            if (!isNaN(pageCount)) {
                scale = mxUtils.getScaleForPageCount(pageCount, graph, pf);
            }
        }

        // 如果页面可见，负坐标会被裁剪或偏移
        var gb = graph.getGraphBounds();
        var border = 0;
        var x0 = 0;
        var y0 = 0;

        // 应用打印缩放
        pf = mxRectangle.fromRectangle(pf);
        pf.width = Math.ceil(pf.width * printScale);
        pf.height = Math.ceil(pf.height * printScale);
        scale *= printScale;

        // 从第一个可见页面开始
        if (!autoOrigin && graph.pageVisible) {
            var layout = graph.getPageLayout();
            x0 -= layout.x * pf.width;
            y0 -= layout.y * pf.height;
        } else {
            autoOrigin = true;
        }

        var preview = PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin);
        preview.open();

        if (print) {
            PrintDialog.printPreview(preview);
        }
    };

    var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
        editorUi.hideDialog();
    });
    cancelBtn.className = 'geBtn';

    if (editorUi.editor.cancelFirst) {
        td.appendChild(cancelBtn);
    }

    if (PrintDialog.previewEnabled) {
        var previewBtn = mxUtils.button(mxResources.get('preview'), function () {
            editorUi.hideDialog();
            preview(false);
        });
        previewBtn.className = 'geBtn';
        td.appendChild(previewBtn);
    }

    var printBtn = mxUtils.button(mxResources.get((!PrintDialog.previewEnabled) ? 'ok' : 'print'), function () {
        editorUi.hideDialog();
        preview(true);
    });
    printBtn.className = 'geBtn gePrimaryBtn';
    td.appendChild(printBtn);

    if (!editorUi.editor.cancelFirst) {
        td.appendChild(cancelBtn);
    }

    row.appendChild(td);
    tbody.appendChild(row);

    table.appendChild(tbody);
    this.container = table;
};

/**
 * 构造一个新的打印对话框。
 */
PrintDialog.printPreview = function (preview) {
    try {
        if (preview.wnd != null) {
            var printFn = function () {
                preview.wnd.focus();
                preview.wnd.print();
                preview.wnd.close();
            };

            // Google Chrome需要一些处理的临时解决方案
            // 延迟以便渲染SVG内容
            // 需要在生产环境中测试
            if (mxClient.IS_GC) {
                window.setTimeout(printFn, 500);
            } else {
                printFn();
            }
        }
    } catch (e) {
        // ignores possible Access Denied
    }
};

/**
 * 构造一个新的打印对话框。
 */
PrintDialog.createPrintPreview = function (graph, scale, pf, border, x0, y0, autoOrigin) {
    var preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
    preview.title = mxResources.get('preview');
    preview.printBackgroundImage = true;
    preview.autoOrigin = autoOrigin;
    var bg = graph.background;

    if (bg == null || bg == '' || bg == mxConstants.NONE) {
        bg = '#ffffff';
    }

    preview.backgroundColor = bg;

    var writeHead = preview.writeHead;

    // 在预览中添加边框
    preview.writeHead = function (doc) {
        writeHead.apply(this, arguments);

        doc.writeln('<style type="text/css">');
        doc.writeln('@media screen {');
        doc.writeln('  body > div { padding:30px;box-sizing:content-box; }');
        doc.writeln('}');
        doc.writeln('</style>');
    };

    return preview;
};

/**
 * 指定是否应启用预览按钮。默认为 true。
 */
PrintDialog.previewEnabled = true;

/**
 * 构造一个新的页面设置对话框。
 */
var PageSetupDialog = function (editorUi) {
    var graph = editorUi.editor.graph;
    var row, td;

    var table = document.createElement('table');
    table.style.width = '100%';
    table.style.height = '100%';
    var tbody = document.createElement('tbody');

    row = document.createElement('tr');

    td = document.createElement('td');
    td.style.verticalAlign = 'top';
    td.style.fontSize = '10pt';
    mxUtils.write(td, mxResources.get('paperSize') + ':');

    row.appendChild(td);

    td = document.createElement('td');
    td.style.verticalAlign = 'top';
    td.style.fontSize = '10pt';

    var accessor = PageSetupDialog.addPageFormatPanel(td, 'pagesetupdialog', graph.pageFormat);

    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');

    td = document.createElement('td');
    mxUtils.write(td, mxResources.get('background') + ':');

    row.appendChild(td);

    td = document.createElement('td');
    td.style.whiteSpace = 'nowrap';

    var backgroundInput = document.createElement('input');
    backgroundInput.setAttribute('type', 'text');
    var backgroundButton = document.createElement('button');

    backgroundButton.style.width = '18px';
    backgroundButton.style.height = '18px';
    backgroundButton.style.marginRight = '20px';
    backgroundButton.style.backgroundPosition = 'center center';
    backgroundButton.style.backgroundRepeat = 'no-repeat';

    var newBackgroundColor = graph.background;

    function updateBackgroundColor() {
        if (newBackgroundColor == null || newBackgroundColor == mxConstants.NONE) {
            backgroundButton.style.backgroundColor = '';
            backgroundButton.style.backgroundImage = 'url(\'' + Dialog.prototype.noColorImage + '\')';
        } else {
            backgroundButton.style.backgroundColor = newBackgroundColor;
            backgroundButton.style.backgroundImage = '';
        }
    };

    updateBackgroundColor();

    mxEvent.addListener(backgroundButton, 'click', function (evt) {
        editorUi.pickColor(newBackgroundColor || 'none', function (color) {
            newBackgroundColor = color;
            updateBackgroundColor();
        });
        mxEvent.consume(evt);
    });

    td.appendChild(backgroundButton);

    mxUtils.write(td, mxResources.get('gridSize') + ':');

    var gridSizeInput = document.createElement('input');
    gridSizeInput.setAttribute('type', 'number');
    gridSizeInput.setAttribute('min', '0');
    gridSizeInput.style.width = '40px';
    gridSizeInput.style.marginLeft = '6px';

    gridSizeInput.value = graph.getGridSize();
    td.appendChild(gridSizeInput);

    mxEvent.addListener(gridSizeInput, 'change', function () {
        var value = parseInt(gridSizeInput.value);
        gridSizeInput.value = Math.max(1, (isNaN(value)) ? graph.getGridSize() : value);
    });

    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');
    td = document.createElement('td');

    mxUtils.write(td, mxResources.get('image') + ':');

    row.appendChild(td);
    td = document.createElement('td');

    var changeImageLink = document.createElement('a');
    changeImageLink.style.textDecoration = 'underline';
    changeImageLink.style.cursor = 'pointer';
    changeImageLink.style.color = '#a0a0a0';

    var newBackgroundImage = graph.backgroundImage;

    function updateBackgroundImage() {
        if (newBackgroundImage == null) {
            changeImageLink.removeAttribute('title');
            changeImageLink.style.fontSize = '';
            changeImageLink.innerHTML = mxUtils.htmlEntities(mxResources.get('change')) + '...';
        } else {
            changeImageLink.setAttribute('title', newBackgroundImage.src);
            changeImageLink.style.fontSize = '11px';
            changeImageLink.innerHTML = mxUtils.htmlEntities(newBackgroundImage.src.substring(0, 42)) + '...';
        }
    };

    mxEvent.addListener(changeImageLink, 'click', function (evt) {
        editorUi.showBackgroundImageDialog(function (image, failed) {
            if (!failed) {
                newBackgroundImage = image;
                updateBackgroundImage();
            }
        }, newBackgroundImage);

        mxEvent.consume(evt);
    });

    updateBackgroundImage();

    td.appendChild(changeImageLink);

    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.style.paddingTop = '16px';
    td.setAttribute('align', 'right');

    var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
        editorUi.hideDialog();
    });
    cancelBtn.className = 'geBtn';

    if (editorUi.editor.cancelFirst) {
        td.appendChild(cancelBtn);
    }

    var applyBtn = mxUtils.button(mxResources.get('apply'), function () {
        editorUi.hideDialog();
        var gridSize = parseInt(gridSizeInput.value);

        if (!isNaN(gridSize) && graph.gridSize !== gridSize) {
            graph.setGridSize(gridSize);
        }

        var change = new ChangePageSetup(editorUi, newBackgroundColor,
            newBackgroundImage, accessor.get());
        change.ignoreColor = graph.background == newBackgroundColor;

        var oldSrc = (graph.backgroundImage != null) ? graph.backgroundImage.src : null;
        var newSrc = (newBackgroundImage != null) ? newBackgroundImage.src : null;

        change.ignoreImage = oldSrc === newSrc;

        if (graph.pageFormat.width != change.previousFormat.width ||
            graph.pageFormat.height != change.previousFormat.height ||
            !change.ignoreColor || !change.ignoreImage) {
            graph.model.execute(change);
        }
    });
    applyBtn.className = 'geBtn gePrimaryBtn';
    td.appendChild(applyBtn);

    if (!editorUi.editor.cancelFirst) {
        td.appendChild(cancelBtn);
    }

    row.appendChild(td);
    tbody.appendChild(row);

    table.appendChild(tbody);
    this.container = table;
};

/**
 *
 */
PageSetupDialog.addPageFormatPanel = function (div, namePostfix, pageFormat, pageFormatListener) {
    var formatName = 'format-' + namePostfix;

    var portraitCheckBox = document.createElement('input');
    portraitCheckBox.setAttribute('name', formatName);
    portraitCheckBox.setAttribute('type', 'radio');
    portraitCheckBox.setAttribute('value', 'portrait');

    var landscapeCheckBox = document.createElement('input');
    landscapeCheckBox.setAttribute('name', formatName);
    landscapeCheckBox.setAttribute('type', 'radio');
    landscapeCheckBox.setAttribute('value', 'landscape');

    var paperSizeSelect = document.createElement('select');
    paperSizeSelect.style.marginBottom = '8px';
    paperSizeSelect.style.width = '202px';

    var formatDiv = document.createElement('div');
    formatDiv.style.marginLeft = '4px';
    formatDiv.style.width = '210px';
    formatDiv.style.height = '24px';

    portraitCheckBox.style.marginRight = '6px';
    formatDiv.appendChild(portraitCheckBox);

    var portraitSpan = document.createElement('span');
    portraitSpan.style.maxWidth = '100px';
    mxUtils.write(portraitSpan, mxResources.get('portrait'));
    formatDiv.appendChild(portraitSpan);

    landscapeCheckBox.style.marginLeft = '10px';
    landscapeCheckBox.style.marginRight = '6px';
    formatDiv.appendChild(landscapeCheckBox);

    var landscapeSpan = document.createElement('span');
    landscapeSpan.style.width = '100px';
    mxUtils.write(landscapeSpan, mxResources.get('landscape'));
    formatDiv.appendChild(landscapeSpan)

    var customDiv = document.createElement('div');
    customDiv.style.marginLeft = '4px';
    customDiv.style.width = '210px';
    customDiv.style.height = '24px';

    var widthInput = document.createElement('input');
    widthInput.setAttribute('size', '7');
    widthInput.style.textAlign = 'right';
    customDiv.appendChild(widthInput);
    mxUtils.write(customDiv, ' in x ');

    var heightInput = document.createElement('input');
    heightInput.setAttribute('size', '7');
    heightInput.style.textAlign = 'right';
    customDiv.appendChild(heightInput);
    mxUtils.write(customDiv, ' in');

    formatDiv.style.display = 'none';
    customDiv.style.display = 'none';

    var pf = new Object();
    var formats = PageSetupDialog.getFormats();

    for (var i = 0; i < formats.length; i++) {
        var f = formats[i];
        pf[f.key] = f;

        var paperSizeOption = document.createElement('option');
        paperSizeOption.setAttribute('value', f.key);
        mxUtils.write(paperSizeOption, f.title);
        paperSizeSelect.appendChild(paperSizeOption);
    }

    var customSize = false;

    function listener(sender, evt, force) {
        if (force || (widthInput != document.activeElement && heightInput != document.activeElement)) {
            var detected = false;

            for (var i = 0; i < formats.length; i++) {
                var f = formats[i];

                // 选择自定义的特殊情况
                if (customSize) {
                    if (f.key == 'custom') {
                        paperSizeSelect.value = f.key;
                        customSize = false;
                    }
                } else if (f.format != null) {
                    // 修复之前A4和A5页面尺寸的错误值
                    if (f.key == 'a4') {
                        if (pageFormat.width == 826) {
                            pageFormat = mxRectangle.fromRectangle(pageFormat);
                            pageFormat.width = 827;
                        } else if (pageFormat.height == 826) {
                            pageFormat = mxRectangle.fromRectangle(pageFormat);
                            pageFormat.height = 827;
                        }
                    } else if (f.key == 'a5') {
                        if (pageFormat.width == 584) {
                            pageFormat = mxRectangle.fromRectangle(pageFormat);
                            pageFormat.width = 583;
                        } else if (pageFormat.height == 584) {
                            pageFormat = mxRectangle.fromRectangle(pageFormat);
                            pageFormat.height = 583;
                        }
                    }

                    if (pageFormat.width == f.format.width && pageFormat.height == f.format.height) {
                        paperSizeSelect.value = f.key;
                        portraitCheckBox.setAttribute('checked', 'checked');
                        portraitCheckBox.defaultChecked = true;
                        portraitCheckBox.checked = true;
                        landscapeCheckBox.removeAttribute('checked');
                        landscapeCheckBox.defaultChecked = false;
                        landscapeCheckBox.checked = false;
                        detected = true;
                    } else if (pageFormat.width == f.format.height && pageFormat.height == f.format.width) {
                        paperSizeSelect.value = f.key;
                        portraitCheckBox.removeAttribute('checked');
                        portraitCheckBox.defaultChecked = false;
                        portraitCheckBox.checked = false;
                        landscapeCheckBox.setAttribute('checked', 'checked');
                        landscapeCheckBox.defaultChecked = true;
                        landscapeCheckBox.checked = true;
                        detected = true;
                    }
                }
            }

            // 选择列表中最后一个的自定义格式
            if (!detected) {
                widthInput.value = pageFormat.width / 100;
                heightInput.value = pageFormat.height / 100;
                portraitCheckBox.setAttribute('checked', 'checked');
                paperSizeSelect.value = 'custom';
                formatDiv.style.display = 'none';
                customDiv.style.display = '';
            } else {
                formatDiv.style.display = '';
                customDiv.style.display = 'none';
            }
        }
    };

    listener();

    div.appendChild(paperSizeSelect);
    mxUtils.br(div);

    div.appendChild(formatDiv);
    div.appendChild(customDiv);

    var currentPageFormat = pageFormat;

    var update = function (evt, selectChanged) {
        var f = pf[paperSizeSelect.value];

        if (f.format != null) {
            widthInput.value = f.format.width / 100;
            heightInput.value = f.format.height / 100;
            customDiv.style.display = 'none';
            formatDiv.style.display = '';
        } else {
            formatDiv.style.display = 'none';
            customDiv.style.display = '';
        }

        var wi = parseFloat(widthInput.value);

        if (isNaN(wi) || wi <= 0) {
            widthInput.value = pageFormat.width / 100;
        }

        var hi = parseFloat(heightInput.value);

        if (isNaN(hi) || hi <= 0) {
            heightInput.value = pageFormat.height / 100;
        }

        var newPageFormat = new mxRectangle(0, 0,
            Math.floor(parseFloat(widthInput.value) * 100),
            Math.floor(parseFloat(heightInput.value) * 100));

        if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked) {
            newPageFormat = new mxRectangle(0, 0, newPageFormat.height, newPageFormat.width);
        }

        // 初次选择自定义不应更新页面格式以避免更新组合框
        if ((!selectChanged || !customSize) && (newPageFormat.width != currentPageFormat.width ||
                newPageFormat.height != currentPageFormat.height)) {
            currentPageFormat = newPageFormat;

            // 更新页面格式并重新加载格式面板
            if (pageFormatListener != null) {
                pageFormatListener(currentPageFormat);
            }
        }
    };

    mxEvent.addListener(portraitSpan, 'click', function (evt) {
        portraitCheckBox.checked = true;
        update(evt);
        mxEvent.consume(evt);
    });

    mxEvent.addListener(landscapeSpan, 'click', function (evt) {
        landscapeCheckBox.checked = true;
        update(evt);
        mxEvent.consume(evt);
    });

    mxEvent.addListener(widthInput, 'blur', update);
    mxEvent.addListener(widthInput, 'click', update);
    mxEvent.addListener(heightInput, 'blur', update);
    mxEvent.addListener(heightInput, 'click', update);
    mxEvent.addListener(landscapeCheckBox, 'change', update);
    mxEvent.addListener(portraitCheckBox, 'change', update);
    mxEvent.addListener(paperSizeSelect, 'change', function (evt) {
        // 处理选择自定义的特殊情况
        customSize = paperSizeSelect.value == 'custom';
        update(evt, true);
    });

    update();

    return {
        set: function (value) {
            pageFormat = value;
            listener(null, null, true);
        },
        get: function () {
            return currentPageFormat;
        },
        widthInput: widthInput,
        heightInput: heightInput
    };
};

/**
 *
 */
PageSetupDialog.getFormats = function () {
    return [{
            key: 'letter',
            title: 'US-Letter (8,5" x 11")',
            format: mxConstants.PAGE_FORMAT_LETTER_PORTRAIT
        },
        {
            key: 'legal',
            title: 'US-Legal (8,5" x 14")',
            format: new mxRectangle(0, 0, 850, 1400)
        },
        {
            key: 'tabloid',
            title: 'US-Tabloid (11" x 17")',
            format: new mxRectangle(0, 0, 1100, 1700)
        },
        {
            key: 'executive',
            title: 'US-Executive (7" x 10")',
            format: new mxRectangle(0, 0, 700, 1000)
        },
        {
            key: 'a0',
            title: 'A0 (841 mm x 1189 mm)',
            format: new mxRectangle(0, 0, 3300, 4681)
        },
        {
            key: 'a1',
            title: 'A1 (594 mm x 841 mm)',
            format: new mxRectangle(0, 0, 2339, 3300)
        },
        {
            key: 'a2',
            title: 'A2 (420 mm x 594 mm)',
            format: new mxRectangle(0, 0, 1654, 2336)
        },
        {
            key: 'a3',
            title: 'A3 (297 mm x 420 mm)',
            format: new mxRectangle(0, 0, 1169, 1654)
        },
        {
            key: 'a4',
            title: 'A4 (210 mm x 297 mm)',
            format: mxConstants.PAGE_FORMAT_A4_PORTRAIT
        },
        {
            key: 'a5',
            title: 'A5 (148 mm x 210 mm)',
            format: new mxRectangle(0, 0, 583, 827)
        },
        {
            key: 'a6',
            title: 'A6 (105 mm x 148 mm)',
            format: new mxRectangle(0, 0, 413, 583)
        },
        {
            key: 'a7',
            title: 'A7 (74 mm x 105 mm)',
            format: new mxRectangle(0, 0, 291, 413)
        },
        {
            key: 'b4',
            title: 'B4 (250 mm x 353 mm)',
            format: new mxRectangle(0, 0, 980, 1390)
        },
        {
            key: 'b5',
            title: 'B5 (176 mm x 250 mm)',
            format: new mxRectangle(0, 0, 690, 980)
        },
        {
            key: '16-9',
            title: '16:9 (1600 x 900)',
            format: new mxRectangle(0, 0, 900, 1600)
        },
        {
            key: '16-10',
            title: '16:10 (1920 x 1200)',
            format: new mxRectangle(0, 0, 1200, 1920)
        },
        {
            key: '4-3',
            title: '4:3 (1600 x 1200)',
            format: new mxRectangle(0, 0, 1200, 1600)
        },
        {
            key: 'custom',
            title: mxResources.get('custom'),
            format: null
        }
    ];
};

/**
 * 构造一个新的文件名对话框。
 */
var FilenameDialog = function (editorUi, filename, buttonText, fn, label, validateFn, content, helpLink, closeOnBtn, cancelFn, hints, w) {
    closeOnBtn = (closeOnBtn != null) ? closeOnBtn : true;
    var row, td;

    var table = document.createElement('table');
    var tbody = document.createElement('tbody');
    table.style.marginTop = '8px';

    row = document.createElement('tr');

    td = document.createElement('td');
    td.style.whiteSpace = 'nowrap';
    td.style.fontSize = '10pt';
    td.style.width = (hints) ? '80px' : '120px';
    mxUtils.write(td, (label || mxResources.get('filename')) + ':');

    row.appendChild(td);

    var nameInput = document.createElement('input');
    nameInput.setAttribute('value', filename || '');
    nameInput.style.marginLeft = '4px';
    nameInput.style.width = (w != null) ? w + 'px' : '180px';

    var genericBtn = mxUtils.button(buttonText, function () {
        if (validateFn == null || validateFn(nameInput.value)) {
            if (closeOnBtn) {
                editorUi.hideDialog();
            }

            fn(nameInput.value);
        }
    });
    genericBtn.className = 'geBtn gePrimaryBtn';

    this.init = function () {
        if (label == null && content != null) {
            return;
        }

        nameInput.focus();

        if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5) {
            nameInput.select();
        } else {
            document.execCommand('selectAll', false, null);
        }

        // 为链接安装拖放处理程序
        if (Graph.fileSupport) {
            // 设置拖放监听器
            var dlg = table.parentNode;

            if (dlg != null) {
                var graph = editorUi.editor.graph;
                var dropElt = null;

                mxEvent.addListener(dlg, 'dragleave', function (evt) {
                    if (dropElt != null) {
                        dropElt.style.backgroundColor = '';
                        dropElt = null;
                    }

                    evt.stopPropagation();
                    evt.preventDefault();
                });

                mxEvent.addListener(dlg, 'dragover', mxUtils.bind(this, function (evt) {
                    // IE 10 does not implement pointer-events so it can't have a drop highlight
                    if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10)) {
                        dropElt = nameInput;
                        dropElt.style.backgroundColor = '#ebf2f9';
                    }

                    evt.stopPropagation();
                    evt.preventDefault();
                }));

                mxEvent.addListener(dlg, 'drop', mxUtils.bind(this, function (evt) {
                    if (dropElt != null) {
                        dropElt.style.backgroundColor = '';
                        dropElt = null;
                    }

                    if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) {
                        nameInput.value = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
                        genericBtn.click();
                    }

                    evt.stopPropagation();
                    evt.preventDefault();
                }));
            }
        }
    };

    td = document.createElement('td');
    td.style.whiteSpace = 'nowrap';
    td.appendChild(nameInput);
    row.appendChild(td);

    if (label != null || content == null) {
        tbody.appendChild(row);

        if (hints != null) {
            if (editorUi.editor.diagramFileTypes != null) {
                var typeSelect = FilenameDialog.createFileTypes(editorUi, nameInput, editorUi.editor.diagramFileTypes);
                typeSelect.style.marginLeft = '6px';
                typeSelect.style.width = '74px';

                td.appendChild(typeSelect);
                nameInput.style.width = (w != null) ? (w - 40) + 'px' : '140px';
            }

            td.appendChild(FilenameDialog.createTypeHint(editorUi, nameInput, hints));
        }
    }

    if (content != null) {
        row = document.createElement('tr');
        td = document.createElement('td');
        td.colSpan = 2;
        td.appendChild(content);
        row.appendChild(td);
        tbody.appendChild(row);
    }

    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.style.paddingTop = '20px';
    td.style.whiteSpace = 'nowrap';
    td.setAttribute('align', 'right');

    var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
        editorUi.hideDialog();

        if (cancelFn != null) {
            cancelFn();
        }
    });
    cancelBtn.className = 'geBtn';

    if (editorUi.editor.cancelFirst) {
        td.appendChild(cancelBtn);
    }

    if (helpLink != null) {
        var helpBtn = mxUtils.button(mxResources.get('help'), function () {
            editorUi.editor.graph.openLink(helpLink);
        });

        helpBtn.className = 'geBtn';
        td.appendChild(helpBtn);
    }

    mxEvent.addListener(nameInput, 'keypress', function (e) {
        if (e.keyCode == 13) {
            genericBtn.click();
        }
    });

    td.appendChild(genericBtn);

    if (!editorUi.editor.cancelFirst) {
        td.appendChild(cancelBtn);
    }

    row.appendChild(td);
    tbody.appendChild(row);
    table.appendChild(tbody);

    this.container = table;
};

/**
 * 文件名对话框的帮助链接URL。
 * 当用户点击帮助图标时打开的帮助页面链接，默认为null。
 */
FilenameDialog.filenameHelpLink = null;

/**
 * 创建文件类型提示图标。
 * 根据用户输入的文件名自动显示相应的文件类型帮助信息和图标。
 *
 * @param {Object} ui - 编辑器UI实例
 * @param {HTMLElement} nameInput - 文件名输入框元素
 * @param {Array} hints - 文件类型提示数组，包含扩展名和提示信息
 * @returns {HTMLElement} 创建的提示图标元素
 */
FilenameDialog.createTypeHint = function (ui, nameInput, hints) {
    var hint = document.createElement('img');
    hint.style.cssText = 'vertical-align:top;height:16px;width:16px;margin-left:4px;background-repeat:no-repeat;background-position:center bottom;cursor:pointer;';
    mxUtils.setOpacity(hint, 70);

    var nameChanged = function () {
        hint.setAttribute('src', Editor.helpImage);
        hint.setAttribute('title', mxResources.get('help'));

        for (var i = 0; i < hints.length; i++) {
            if (hints[i].ext.length > 0 && nameInput.value.toLowerCase().substring(
                    nameInput.value.length - hints[i].ext.length - 1) == '.' + hints[i].ext) {
                hint.setAttribute('src', mxClient.imageBasePath + '/warning.png');
                hint.setAttribute('title', mxResources.get(hints[i].title));
                break;
            }
        }
    };

    mxEvent.addListener(nameInput, 'keyup', nameChanged);
    mxEvent.addListener(nameInput, 'change', nameChanged);
    mxEvent.addListener(hint, 'click', function (evt) {
        var title = hint.getAttribute('title');

        if (hint.getAttribute('src') == Editor.helpImage) {
            ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
        } else if (title != '') {
            ui.showError(null, title, mxResources.get('help'), function () {
                ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
            }, null, mxResources.get('ok'), null, null, null, 340, 90);
        }

        mxEvent.consume(evt);
    });

    nameChanged();

    return hint;
};

/**
 *
 */
FilenameDialog.createFileTypes = function (editorUi, nameInput, types) {
    var typeSelect = document.createElement('select');

    for (var i = 0; i < types.length; i++) {
        var typeOption = document.createElement('option');
        typeOption.setAttribute('value', i);
        mxUtils.write(typeOption, mxResources.get(types[i].description) +
            ' (.' + types[i].extension + ')');
        typeSelect.appendChild(typeOption);
    }

    mxEvent.addListener(typeSelect, 'change', function (evt) {
        var ext = types[typeSelect.value].extension;
        var idx = nameInput.value.lastIndexOf('.');

        if (idx > 0) {
            var ext = types[typeSelect.value].extension;
            nameInput.value = nameInput.value.substring(0, idx + 1) + ext;
        } else {
            nameInput.value = nameInput.value + '.' + ext;
        }

        if ('createEvent' in document) {
            var changeEvent = document.createEvent('HTMLEvents');
            changeEvent.initEvent('change', false, true);
            nameInput.dispatchEvent(changeEvent);
        } else {
            nameInput.fireEvent('onchange');
        }
    });

    var nameInputChanged = function (evt) {
        var idx = nameInput.value.lastIndexOf('.');
        var active = 0;

        // 查找当前扩展名
        if (idx > 0) {
            var ext = nameInput.value.toLowerCase().substring(idx + 1);

            for (var i = 0; i < types.length; i++) {
                if (ext == types[i].extension) {
                    active = i;
                    break;
                }
            }
        }

        typeSelect.value = active;
    };

    mxEvent.addListener(nameInput, 'change', nameInputChanged);
    mxEvent.addListener(nameInput, 'keyup', nameInputChanged);
    nameInputChanged();

    return typeSelect;
};

/**
 * 静态重写
 */
(function () {
    // 使用HTML作为背景页面（支持网格背景图像）
    mxGraphView.prototype.validateBackgroundPage = function () {
        var graph = this.graph;

        if (graph.container != null && !graph.transparentBackground) {
            if (graph.pageVisible) {
                var bounds = this.getBackgroundPageBounds();

                if (this.backgroundPageShape == null) {
                    // 在图形容器中查找第一个元素
                    var firstChild = graph.container.firstChild;

                    while (firstChild != null && firstChild.nodeType != mxConstants.NODETYPE_ELEMENT) {
                        firstChild = firstChild.nextSibling;
                    }

                    if (firstChild != null) {
                        this.backgroundPageShape = this.createBackgroundPageShape(bounds);
                        this.backgroundPageShape.scale = 1;

                        // IE8 standards has known rendering issues inside mxWindow but not using shadow is worse.
                        this.backgroundPageShape.isShadow = true;
                        this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
                        this.backgroundPageShape.init(graph.container);
                        // 浏览器按正确顺序渲染背景页面所必需的
                        firstChild.style.position = 'absolute';
                        graph.container.insertBefore(this.backgroundPageShape.node, firstChild);
                        this.backgroundPageShape.redraw();

                        this.backgroundPageShape.node.className = 'geBackgroundPage';

                        // 为背景上的双击处理添加监听器
                        mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
                            mxUtils.bind(this, function (evt) {
                                graph.dblClick(evt);
                            })
                        );

                        // 为图形事件在外部调度添加基本监听器
                        // container and finishing the handling of a single gesture
                        mxEvent.addGestureListeners(this.backgroundPageShape.node,
                            mxUtils.bind(this, function (evt) {
                                graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
                            }),
                            mxUtils.bind(this, function (evt) {
                                // 如果鼠标在容器外则隐藏工具提示
                                if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) {
                                    graph.tooltipHandler.hide();
                                }

                                if (graph.isMouseDown && !mxEvent.isConsumed(evt)) {
                                    graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
                                }
                            }),
                            mxUtils.bind(this, function (evt) {
                                graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
                            })
                        );
                    }
                } else {
                    this.backgroundPageShape.scale = 1;
                    this.backgroundPageShape.bounds = bounds;
                    this.backgroundPageShape.redraw();
                }
            } else if (this.backgroundPageShape != null) {
                this.backgroundPageShape.destroy();
                this.backgroundPageShape = null;
            }

            this.validateBackgroundStyles();
        }
    };

    // 更新背景的CSS以绘制网格
    mxGraphView.prototype.validateBackgroundStyles = function () {
        var graph = this.graph;
        var color = (graph.background == null || graph.background == mxConstants.NONE) ? graph.defaultPageBackgroundColor : graph.background;
        var gridColor = (color != null && this.gridColor != color.toLowerCase()) ? this.gridColor : '#ffffff';
        var image = 'none';
        var position = '';

        if (graph.isGridEnabled()) {
            var phase = 10;

            if (mxClient.IS_SVG) {
                // 生成绘制动态网格所需的SVG
                image = unescape(encodeURIComponent(this.createSvgGrid(gridColor)));
                image = (window.btoa) ? btoa(image) : Base64.encode(image, true);
                image = 'url(' + 'data:image/svg+xml;base64,' + image + ')'
                phase = graph.gridSize * this.scale * this.gridSteps;
            } else {
                // 回退到固定尺寸的网格壁纸
                image = 'url(' + this.gridImage + ')';
            }

            var x0 = 0;
            var y0 = 0;

            if (graph.view.backgroundPageShape != null) {
                var bds = this.getBackgroundPageBounds();

                x0 = 1 + bds.x;
                y0 = 1 + bds.y;
            }

            // 计算偏移量以保持网格原点
            position = -Math.round(phase - mxUtils.mod(this.translate.x * this.scale - x0, phase)) + 'px ' +
                -Math.round(phase - mxUtils.mod(this.translate.y * this.scale - y0, phase)) + 'px';
        }

        var canvas = graph.view.canvas;

        if (canvas.ownerSVGElement != null) {
            canvas = canvas.ownerSVGElement;
        }

        if (graph.view.backgroundPageShape != null) {
            graph.view.backgroundPageShape.node.style.backgroundPosition = position;
            graph.view.backgroundPageShape.node.style.backgroundImage = image;
            graph.view.backgroundPageShape.node.style.backgroundColor = color;
            graph.container.className = 'geDiagramContainer geDiagramBackdrop';
            canvas.style.backgroundImage = 'none';
            canvas.style.backgroundColor = '';

            if (window.isSysml) {

                // 创建sysml左上角图框
                // const breadCrumbContainer = EditorUi.prototype.createDivId('geBreadCrumbContainer', 'breadCrumbContainer');
                // graph.view.backgroundPageShape.node.appendChild(breadCrumbContainer);

                // 隐藏网格
                // graph.setGridEnabled(false);
            }
        } else {
            graph.container.className = 'geDiagramContainer';
            canvas.style.backgroundPosition = position;
            canvas.style.backgroundColor = color;
            canvas.style.backgroundImage = image;
        }
    };

    // 返回绘制背景网格所需的SVG。
    mxGraphView.prototype.createSvgGrid = function (color) {
        var tmp = this.graph.gridSize * this.scale;

        while (tmp < this.minGridSize) {
            tmp *= 2;
        }

        var tmp2 = this.gridSteps * tmp;

        // 小型网格线
        var d = [];

        for (var i = 1; i < this.gridSteps; i++) {
            var tmp3 = i * tmp;
            d.push('M 0 ' + tmp3 + ' L ' + tmp2 + ' ' + tmp3 + ' M ' + tmp3 + ' 0 L ' + tmp3 + ' ' + tmp2);
        }

        // KNOWN: Rounding errors for certain scales (eg. 144%, 121% in Chrome, FF and Safari). Workaround
        // in Chrome is to use 100% for the svg size, but this results in blurred grid for large diagrams.
        var size = tmp2;
        var svg = '<svg width="' + size + '" height="' + size + '" xmlns="' + mxConstants.NS_SVG + '">' +
            '<defs><pattern id="grid" width="' + tmp2 + '" height="' + tmp2 + '" patternUnits="userSpaceOnUse">' +
            '<path d="' + d.join(' ') + '" fill="none" stroke="' + color + '" opacity="0.2" stroke-width="1"/>' +
            '<path d="M ' + tmp2 + ' 0 L 0 0 0 ' + tmp2 + '" fill="none" stroke="' + color + '" stroke-width="1"/>' +
            '</pattern></defs><rect width="100%" height="100%" fill="url(#grid)"/></svg>';

        return svg;
    };

    // 为无页面视图和禁用滚动条的网格添加平移功能
    var mxGraphPanGraph = mxGraph.prototype.panGraph;
    mxGraph.prototype.panGraph = function (dx, dy) {
        mxGraphPanGraph.apply(this, arguments);

        if (this.shiftPreview1 != null) {
            var canvas = this.view.canvas;

            if (canvas.ownerSVGElement != null) {
                canvas = canvas.ownerSVGElement;
            }

            var phase = this.gridSize * this.view.scale * this.view.gridSteps;
            var position = -Math.round(phase - mxUtils.mod(this.view.translate.x * this.view.scale + dx, phase)) + 'px ' +
                -Math.round(phase - mxUtils.mod(this.view.translate.y * this.view.scale + dy, phase)) + 'px';
            canvas.style.backgroundPosition = position;
        }
    };

    // 仅在页面内绘制分页符
    mxGraph.prototype.updatePageBreaks = function (visible, width, height) {
        var scale = this.view.scale;
        var tr = this.view.translate;
        var fmt = this.pageFormat;
        var ps = scale * this.pageScale;

        var bounds2 = this.view.getBackgroundPageBounds();

        width = bounds2.width;
        height = bounds2.height;
        var bounds = new mxRectangle(scale * tr.x, scale * tr.y, fmt.width * ps, fmt.height * ps);

        // 如果缩放比例太小则不显示分页符
        visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;

        var horizontalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
        var verticalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
        var right = bounds2.x + width;
        var bottom = bounds2.y + height;

        if (this.horizontalPageBreaks == null && horizontalCount > 0) {
            this.horizontalPageBreaks = [];
        }

        if (this.verticalPageBreaks == null && verticalCount > 0) {
            this.verticalPageBreaks = [];
        }

        var drawPageBreaks = mxUtils.bind(this, function (breaks) {
            if (breaks != null) {
                var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount;

                for (var i = 0; i <= count; i++) {
                    var pts = (breaks == this.horizontalPageBreaks) ? [new mxPoint(Math.round(bounds2.x), Math.round(bounds2.y + (i + 1) * bounds.height)),
                        new mxPoint(Math.round(right), Math.round(bounds2.y + (i + 1) * bounds.height))
                    ] : [new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bounds2.y)),
                        new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bottom))
                    ];

                    if (breaks[i] != null) {
                        breaks[i].points = pts;
                        breaks[i].redraw();
                    } else {
                        var pageBreak = new mxPolyline(pts, this.pageBreakColor);
                        pageBreak.dialect = this.dialect;
                        pageBreak.isDashed = this.pageBreakDashed;
                        pageBreak.pointerEvents = false;
                        pageBreak.init(this.view.backgroundPane);
                        pageBreak.redraw();

                        breaks[i] = pageBreak;
                    }
                }

                for (var i = count; i < breaks.length; i++) {
                    breaks[i].destroy();
                }

                breaks.splice(count, breaks.length - count);
            }
        });

        drawPageBreaks(this.horizontalPageBreaks);
        drawPageBreaks(this.verticalPageBreaks);
    };

    // 禁止从父元素中移除相对子元素和表格行和单元格
    var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
    mxGraphHandler.prototype.shouldRemoveCellsFromParent = function (parent, cells, evt) {
        for (var i = 0; i < cells.length; i++) {
            if (this.graph.isTableCell(cells[i]) || this.graph.isTableRow(cells[i])) {
                return false;
            } else if (this.graph.getModel().isVertex(cells[i])) {
                var geo = this.graph.getCellGeometry(cells[i]);

                if (geo != null && geo.relative) {
                    return false;
                }
            }
        }

        return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
    };

    // 重写以仅对目标终端忽略热点
    var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
    mxConnectionHandler.prototype.createMarker = function () {
        var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);

        marker.intersects = mxUtils.bind(this, function (state, evt) {
            if (this.isConnecting()) {
                return true;
            }

            return mxCellMarker.prototype.intersects.apply(marker, arguments);
        });

        return marker;
    };

    // 创建背景页面形状
    mxGraphView.prototype.createBackgroundPageShape = function (bounds) {
        return new mxRectangleShape(bounds, '#ffffff', this.graph.defaultPageBorderColor);
    };

    // 调整背景页面数量以适应图形
    mxGraphView.prototype.getBackgroundPageBounds = function () {
        var gb = this.getGraphBounds();

        // 计算未缩放、未平移的图形边界
        var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
        var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
        var w = gb.width / this.scale;
        var h = gb.height / this.scale;

        var fmt = this.graph.pageFormat;
        var ps = this.graph.pageScale;

        var pw = fmt.width * ps;
        var ph = fmt.height * ps;

        var x0 = Math.floor(Math.min(0, x) / pw);
        var y0 = Math.floor(Math.min(0, y) / ph);
        var xe = Math.ceil(Math.max(1, x + w) / pw);
        var ye = Math.ceil(Math.max(1, y + h) / ph);

        var rows = xe - x0;
        var cols = ye - y0;

        var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
            (this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);

        return bounds;
    };

    // 为VML中的背景页面添加平移支持
    var graphPanGraph = mxGraph.prototype.panGraph;
    mxGraph.prototype.panGraph = function (dx, dy) {
        graphPanGraph.apply(this, arguments);

        if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
            (!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container))) {
            this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
            this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
        }
    };

    /**
     * 为禁用的菜单项消费点击事件。
     * 防止禁用菜单项响应用户点击操作。
     */
    var mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
    mxPopupMenu.prototype.addItem = function (title, image, funct, parent, iconCls, enabled, selectabled) {
        var result = mxPopupMenuAddItem.apply(this, arguments);

        if (enabled != null && !enabled) {
            mxEvent.addListener(result, 'mousedown', function (evt) {
                mxEvent.consume(evt);
            });
        }

        return result;
    };

    /**
     * 优先选择表格而非单元格和行。
     * 确保在表格结构中首先选中表格本身。
     */
    var mxGraphHandlerIsPropagateSelectionCell = mxGraphHandler.prototype.isPropagateSelectionCell;
    mxGraphHandler.prototype.isPropagateSelectionCell = function (cell, immediate, me) {
        var result = false;
        var parent = this.graph.model.getParent(cell)

        if (immediate) {
            var geo = (this.graph.model.isEdge(cell)) ? null :
                this.graph.getCellGeometry(cell);

            result = !this.graph.model.isEdge(parent) &&
                !this.graph.isSiblingSelected(cell) &&
                ((geo != null && geo.relative) ||
                    !this.graph.isContainer(parent) ||
                    this.graph.isPart(cell));
        } else {
            result = mxGraphHandlerIsPropagateSelectionCell.apply(this, arguments);

            if (this.graph.isTableCell(cell) || this.graph.isTableRow(cell)) {
                var table = parent;

                if (!this.graph.isTable(table)) {
                    table = this.graph.model.getParent(table);
                }

                result = !this.graph.selectionCellsHandler.isHandled(table) ||
                    (this.graph.isCellSelected(table) && this.graph.isToggleEvent(me.getEvent())) ||
                    (this.graph.isCellSelected(cell) && !this.graph.isToggleEvent(me.getEvent())) ||
                    (this.graph.isTableCell(cell) && this.graph.isCellSelected(parent));
            }
        }

        return result;
    };

    /**
     * 返回最后选中的祖先元素。
     * 在元素层级结构中查找并返回最上层的选中元素。
     */
    mxPopupMenuHandler.prototype.getCellForPopupEvent = function (me) {
        var cell = me.getCell();
        var model = this.graph.getModel();
        var parent = model.getParent(cell);
        var state = this.graph.view.getState(parent);
        var selected = this.graph.isCellSelected(cell);

        while (state != null && (model.isVertex(parent) || model.isEdge(parent))) {
            var temp = this.graph.isCellSelected(parent);
            selected = selected || temp;

            if (temp || (!selected && (this.graph.isTableCell(cell) ||
                    this.graph.isTableRow(cell)))) {
                cell = parent;
            }

            parent = model.getParent(parent);
        }

        return cell;
    };

})();